module dungeon.character;

import std.string;
import std.math;

import derelict.opengl.gl;

import nade.time;
import nade.random;
import nade.vector;
import nade.region;
import nade.sprite;
import nade.font;
import nade.color;

import dungeon.game;
import dungeon.stats;
import dungeon.equipment;
import dungeon.inventory;
import dungeon.weapon;
import dungeon.attack;
import dungeon.map;
import dungeon.map_object;
import dungeon.mover;


class Character {
	struct Buff {
		string		name;
		Sprite		sprite;
		Stats		stats;
		real		endTime;
	}

	this(Game game)
	{
		_game = game;
		_statsBase = new Stats();
		_statsModified = new Stats();
		_loadout = new Loadout();
		_inventory = new Inventory();

		foreach(ref value; _statsBase.values[0 .. (StatInfo.get("CHA").handle + 1)])
			value = random(0.0, 18.0);
	}

	void render(const Vector2 position)
	{
		glPushMatrix();
		position.translate;
		_sprite.render(Vector2(1, 1));
		glPopMatrix();
	}

	bool canEquip(Equipment item)
	{
		return _statsBase.checkMin(item.statsRequired);
	}

	real stat(uint handle)
	{
		real result = _statsBase.evaluate(handle) + _statsModified[handle] + _loadout.stat(handle);
		foreach(buff; _buffs)
		{
			if(buff.endTime > curTime) result += buff.stats[handle];
		}
		return result;
	}
	real stat(string id)
	{
		return stat(StatInfo.get(id).handle);
	}
	Stats stats()
	{
		auto result = new Stats;
		for(uint i = 0;  i < StatInfo.count;  ++i) result[i] = stat(i);
		return result;
	}

	real statNaked(uint handle)
	{
		real result = _statsBase.evaluate(handle) + _loadout.stat(handle);
		foreach(buff; _buffs)
		{
			if(buff.endTime > curTime) result += buff.stats[handle];
		}
		return result;
	}
	real statNaked(string id)
	{
		return statNaked(StatInfo.get(id).handle);
	}
	Stats statsNaked()
	{
		auto result = new Stats;
		for(uint i = 0;  i < StatInfo.count;  ++i) result[i] = statNaked(i);
		return result;
	}

	void modify(Stats stats)
	{
		_statsModified = _statsModified + stats;
	}
	void resetMods()
	{
		foreach(ref value; _statsModified) value = 0;
	}

	void addBuff(string name, Sprite sprite, Stats stats, real duration)
	{
		_buffs ~= Buff(name, sprite, stats, curTime + duration);
	}

	Weapon weapon(real range = 0)
	{
		auto weapon = cast(Weapon)loadout.get(EquipmentType.MELEE);
		if(weapon !is null  &&  range <= weapon.attack.range) return weapon;

		weapon = cast(Weapon)loadout.get(EquipmentType.RANGED);
		if(weapon !is null  &&  range <= weapon.attack.range) return weapon;

		weapon = cast(Weapon)_game.getItem("unarmed");
		if(weapon !is null  &&  range <= weapon.attack.range) return weapon;

		return null;
	}

	Sprite sprite() { return _sprite; }
	Sprite sprite(Sprite rhs) { return _sprite = rhs; }

	Stats statsBase() { return _statsBase; }
	Stats statsMod() { return _statsModified; }
	Loadout loadout() { return _loadout; }
	Inventory inventory() { return _inventory; }

	protected {
		Sprite			_sprite;

		Game			_game;
		Stats			_statsBase;
		Stats			_statsModified;
		Loadout			_loadout;
		Inventory		_inventory;
		Buff[]			_buffs;
	}
}
/* 

class CharacterWindow : LabelledWindow {
	this(Character character)
	{
		super("Character");

		_character = character;
	}

	override void renderWindow(const Region2 area)
	{
		super.renderWindow(area);

		for(uint i = 0;  i < StatInfo.count;  ++i)
			font.print(area.min.x + 4, area.max.y - i*14 - 30, format("%7s: %.1f", StatInfo.get(i).id, _character.stat(i)), 14);
	}

	// override Coordinate2 sizeMin() { return Coordinate2(200, 268); }
	// override Coordinate2 sizeMax() { return sizeMin; }

	protected {
		Character		_character;
	}
}
 */

class CharacterObject : MapObject, IIterator {
	this(Map map, Character character)
	{
		super(map);
		map.addIterator(this);

		_character = character;

		_moving = false;
		_mover = new Mover;
	}

	override void render()
	{
		_character.render(_mover.position);
		foreach(effect; _effects.keys) _map.addEffect(effect);
	}

	override void iterate()
	{
	}

	void addEffect(IEffect effect)
	{
		_effects[effect] = true;
	}
	void removeEffect(IEffect effect)
	{
		_effects.remove(effect);
	}

	void attack(CharacterObject target, Attack attack)
	{
		real damage = attack.damage(_character);
		target.attacked(this, damage);
	}
	void attacked(CharacterObject attacker, real damage)
	{
		_character._statsModified["HP"] = _character._statsModified["HP"] - damage;
		if(_character.stat("HP") <= 0)
			remove;
		else
			new NumberEffect(this, damage, Color.WHITE);
	}

	void remove()
	{
		leaveTile(_mover.from);
		leaveTile(_mover.to);
		_map.removeIterator(this);
	}

	void teleport(Vector2i coord)
	{
		remove;
		_mover.from = _mover.to = coord;
		enterTile(coord);
		_map.addIterator(this);
		_mover.travel = 0;
		_moving = false;
	}

	override bool passable() { return false; }
	override uint zIndex() { return 5; }

	Vector2 position() { return _mover.position; }
	Character character() { return _character; }

	protected {
		Character			_character;

		Mover				_mover;
		bool				_moving;
		bool[IEffect]		_effects;
	}
}


class NumberEffect : IIterator, IEffect {
	protected {
		CharacterObject		_target;
		int					_value;
		Color				_color;
		Vector2				_offset;
	}

	this(CharacterObject target, real value, const(Color) color)
	{
		target.map.addIterator(this);

		_target = target;
		_target.addEffect(this);
		_value = cast(int)round(value);
		_color = color;
	}

	void render()
	{
		font.printCenter(_target.position.x + _offset.x, _target.position.y + _offset.y, format("%d", _value), 0.25, _color);
	}

	void iterate()
	{
		_offset.x += random(-0.35, 0.35)*frameTime;
		_offset.y += frameTime*0.35;
		if(_offset.y > 1) remove;
	}

	void remove()
	{
		_target.map.removeIterator(this);
		_target.removeEffect(this);
	}

	CharacterObject target() { return _target; }
}
